
local assert     = assert
local require    = require    
local bit        = bit
local coroutine  = coroutine
local debug      = debug
local io         = io
local pairs      = pairs
local ipairs     = ipairs
local math       = math
local os         = os
local print      = print
local pcall      = pcall
local xpcall     = xpcall
local rawget     = rawget
local rawset     = rawset
local select     = select
local string     = string
local table      = table
local tonumber   = tonumber
local tostring	 = tostring
local error      = error
local type       = type
local unpack     = unpack
local setmetatable = setmetatable
local getmetatable = getmetatable
local ngx = ngx
local math_floor = math.floor

local string_format = string.format

local debug_getinfo = debug.getinfo
local os_date = os.date
local io_open = io.open

local table_insert = table.insert;
local table_concat = table.concat;

local FOO = {
	_VERSION = '0.12.1';
	
	--path root is nginx
	root_dir           = 'logs/miniw/';
	debug_log_path     = 'logs/debug.log';
	debug_uin_log_path = 'logs/uin/';	

	new_root_dir       = './logs/miniworld/';
	buff_io_wait       = 3;
};


--快速日志
--return  false=不打印  true=打印
FOO.fast = function()
	if  ngx.cache.debug_log == 1 then
		return true
	end

	if     ngx.ctx.log == 0 then
		return false   --快速跳过
	elseif ngx.ctx.log == 1 then
		return true    --打印
	elseif ngx.req.get_uri_args().log == "1" then
		ngx.ctx.log = 1
		return true    --打印
	else
		ngx.ctx.log = 0
		return false   --不打印
	end
end


-- 打印到输出界面
FOO.debug = function(txt)
	if     ngx.ctx.log == 0 then
		--快速跳过
	elseif ngx.ctx.log == 1 then
		---inline fast
		if  txt then
			ngx.say( '-- ' .. os_date('%X') .. ' ' .. FOO.getFileAndLine() .. ' ' .. txt .. '<br>' );
		else
			ngx.say( '-- ' .. os_date('%X') .. ' ' .. FOO.getFileAndLine() .. ' nil<br>' );
		end
	else
		if  ngx.req.get_uri_args().log == "1" then
			ngx.ctx.log = 1
			---inline fast
			if  txt then
				ngx.say( '-- ' .. os_date('%X') .. ' ' .. FOO.getFileAndLine() .. ' ' .. txt .. '<br>' );
			else
				ngx.say( '-- ' .. os_date('%X') .. ' ' .. FOO.getFileAndLine() .. ' nil<br>' );
			end
		else
			ngx.ctx.log = 0
		end
	end


	---uin log
	if  ngx.cache.debug_log == 1 then
		---inline fast
		if  txt then
			txt = os_date('%X') .. ' ' .. FOO.getFileAndLine() .. ' ' .. txt
		else
			txt = os_date('%X') .. ' ' .. FOO.getFileAndLine() .. ' nil'
		end
		if  ngx.ctx.m_params and ngx.ctx.m_params.uin then
			FOO.log2fileBuff( FOO.debug_uin_log_path .. ngx.ctx.m_params.uin .. ".log" , (txt or ""), true );
		else
			FOO.log2fileBuff( FOO.debug_log_path, (txt or ""), true );
		end
	end

end


---获得文件的名字和行
FOO.getFileAndLine = function()
	local code_ = debug_getinfo( 3 )
	local desc_ = code_.short_src
	local offset_
	while true do
		local index_ = string.find( desc_, '/', offset_ )
		if  index_ then
			offset_ = index_ + 1
		else
			break
		end
	end
	if  offset_ then
		desc_ = string.sub( desc_, offset_ )
	end
	return desc_ .. '(' .. code_.currentline .. ')'
end


-- 打印到输出界面，传入一个list
FOO.debug_list = function( ... )
	if  not FOO.fast() then
		return   ---不打印
	end

	local new_txt = {}
	for i = 1, select("#", ...) do
		local arg_ = select(i, ...)
		if  type(arg_) == 'string' or type(arg_) == 'number' then
			--as normal
		else
			arg_ = FOO.to_str(arg_)
		end
		new_txt[ #new_txt + 1] = arg_ or 'nil'
	end

	FOO.debug( table_concat(new_txt, ', ') )
end


--uu.to_str()
FOO.to_str = function( v )
	local t_ = type(v)
	if  t_ == 'string' or t_ == 'number' then
		return v .. "(" .. t_ .. ")"
	elseif t_ == 'nil' then
		return 'nil' .. "(" .. t_ .. ")"
	elseif  t_ == 'boolean' then
		if  v then
			return 'true' .. "(" .. t_ .. ")"
		else
			return 'false' .. "(" .. t_ .. ")"
		end
	elseif t_ == 'table' then
		return table.tostring(v) .. "(" .. t_ .. ")"
	elseif v == ngx.null then
		return "not_found_ngx_null" .. "(" .. t_ .. ")"
	else
		return tostring(v) .. "(" .. t_ .. ")"
	end
end



-- 错误日志
-- trace_info: 定位日志，不使用 traceback()
FOO.error = function(txt, trace_info_)
	FOO.debug( "ERROR:" .. (txt or "nil") );
	FOO.day( "error",   (txt or "nil") );
	if  trace_info_ then
		ngx.log( ngx.ALERT, "--------" .. (txt or "nil") .. " trace_info=" .. trace_info_ );
	else
		ngx.log( ngx.ALERT, "--------" .. (txt or "nil") .. "\n" .. debug.traceback() );
	end
end



--打印到文件
FOO.log2file = function(filepath, txt)
	local file = io_open(filepath, "a");   --a+
	if  file then
		file:write( txt .. '\n');
		file:close();
	end
end;


--打印到文件buffer
FOO.writeBuff = {}
FOO.last_write_time = 0

FOO.do_flush = function()
	FOO.last_write_time = ngx.now()

	--开始写入
	local swap_ = FOO.writeBuff;
	FOO.writeBuff = {};

	for fn, v in pairs(swap_) do
		local str_ = table_concat( v )   --合并写入
		local file = io_open(fn, "a");    --a+
		if  file then
			file:write( str_ );
			file:close();
		else
			ngx.log(ngx.ERR, "fail to write " .. fn .. ": " .. str_)
		end
	end
	FOO.in_delay_flush = false
end

FOO.delay_flush = function()
	if FOO.in_delay_flush then
		return
	end
	FOO.in_delay_flush = true
	local _try_flush = function()
		if FOO.in_delay_flush then
			FOO.do_flush()
		end
	end
	ngx.timer.at(FOO.buff_io_wait, _try_flush)
end

FOO.log2fileBuff = function(filepath, txt, local_write_ )

	--是否打印远程日志
	if  local_write_ then
		--写本地
	elseif  ngx.ctx.WWW_log2fileBuff then
		ngx.ctx.WWW_log2fileBuff( filepath, txt )
		return
	end

	--加入写入队列
	if  not FOO.writeBuff[ filepath ] then
		FOO.writeBuff[ filepath ] = {}
	end

	table_insert( FOO.writeBuff[ filepath ], txt .. '\n' )

	--是否需要写入
	local now_ = ngx.now();
	if  now_ - FOO.last_write_time > FOO.buff_io_wait then
		FOO.do_flush()
	else
		FOO.delay_flush()
	end

	--local file = io_open(filepath, "a+");
	--if  file then
		--file:write( txt .. '\n');
		--file:close();
	--end
end



--统一化ip 123.123.123.123
FOO.align_ip = function(ip)
	if  ip then
		while #ip < 15 do
			ip = ip .. " "
		end
	end
	return ip
end

-------------------------------------------------------------------------------------------------------------
-- 接口函数	: FOO.exist_dir
-- 说明		: 目录是否存在(相对FOO.new_root_dir)
-- 参数  	: dir_name						目录名称
-- 返回值   : 目录存在返回true，否则返回false
-------------------------------------------------------------------------------------------------------------
function FOO.exist_dir(dir_name)
	local file = io_open(dir_name, "r")
	if file then file:close() end
	return file ~= nil	
end


-------------------------------------------------------------------------------------------------------------
-- 接口函数	: FOO.log_by_day     本行数只提供给日志服务器使用
-- 说明		: 按天打印
-- 参数  	: mod_prefix					模块前缀
-- 参数  	: log_txt						日志内容
-- 返回值   : 无
-------------------------------------------------------------------------------------------------------------
function FOO.log_by_day(mod_prefix, log_txt, is_simple)        --is_simple 默认0=完全打印  1=最简单  2=打印时间 不打印IP
	local date_name = os_date("%Y%m%d")
	--if not FOO.exist_dir(date_name) then
		--os.execute('mkdir -p '..FOO.new_root_dir..'/'..date_name)
	--end

    local file_path = string_format(FOO.new_root_dir.."/%s/%s", date_name, tostring(mod_prefix))

	if  is_simple == 1 then
		FOO.log2fileBuff(file_path, log_txt)   --简化信息
	elseif is_simple == 2 then
		--local new_txt = os_date('%X') .. '|-|' .. FOO.get_all_cost() .. '|'  .. log_txt;
		local new_txt = table_concat( { os_date('%X'), '-', FOO.get_all_cost(), log_txt }, '|' )
		FOO.log2fileBuff(file_path, new_txt)		
	else
		local new_txt = table_concat( { os_date('%X'), FOO.align_ip(ngx.ctx.xff or ngx.var.remote_addr or "nil"),
				 FOO.get_all_cost(), log_txt }, '|' )
		FOO.log2fileBuff(file_path, new_txt)
	end

end


--- 将按条日志打印到指定一个目录 (新版sdk用来打印统计日志使用 )
--- dir= './logs/realtime/'  此目录必须提前建立
--- 例子 /usr/local/openresty/nginx/logs/realtime/20210303/70000_e0_rt_105
function FOO.log_by_day_dir( mod_prefix, log_txt, is_simple, dir_ )
	local date_name = os_date("%Y%m%d")

	local file_path = string_format( dir_.."/%s/%s", date_name, tostring(mod_prefix) )

	if  is_simple == 1 then
		FOO.log2fileBuff(file_path, log_txt)   --简化信息
	elseif is_simple == 2 then
		--local new_txt = os_date('%X') .. '|-|' .. FOO.get_all_cost() .. '|'  .. log_txt;
		local new_txt = table_concat( { os_date('%X'), '-', FOO.get_all_cost(), log_txt }, '|' )
		FOO.log2fileBuff(file_path, new_txt)
	else
		local new_txt = table_concat( { os_date('%X'), FOO.align_ip(ngx.ctx.xff or ngx.var.remote_addr or "nil"),
										FOO.get_all_cost(), log_txt }, '|' )
		FOO.log2fileBuff(file_path, new_txt)
	end
end


--只获得结果 不打印 (远程日志使用)
function FOO.log_by_day_params(mod_prefix, log_txt)
	local date_name = os_date("%Y%m%d")
    local file_path = string_format(FOO.new_root_dir.."/%s/%s", date_name, tostring(mod_prefix))
	--local new_txt = os_date('%X') .. '|' .. FOO.align_ip(ngx.ctx.xff or ngx.var.remote_addr or "nil")
	--		.. '|' .. FOO.get_all_cost() .. '|'  .. log_txt;
	local new_txt = table_concat( { os_date('%X'), FOO.align_ip(ngx.ctx.xff or ngx.var.remote_addr or "nil"),
									FOO.get_all_cost(), log_txt }, '|' )
    return file_path, new_txt
end


--计算时间
FOO.get_all_cost = function()
	if  ngx.ctx.timer_start_time then
		return  math_floor( (ngx.now() - ngx.ctx.timer_start_time ) * 1000 + 0.5 );
	else
		return  math_floor( (ngx.now() - ngx.req.start_time()) * 1000 + 0.5 );
	end
end


--打印按天日志，按连续字段打  logs/my_name_20160909.log
FOO.day_list = function( name, ... )
	local path_ = FOO.root_dir .. name .. '_' .. os_date( "%Y%m%d" ) .. '.log';
	local new_txt =	{
		os_date('%X'),
		FOO.align_ip(ngx.ctx.xff or ngx.var.remote_addr or "nil"),
		FOO.get_all_cost(),
	}

	for i = 1, select("#", ...) do
		local arg_ = select(i, ...)
		new_txt[ #new_txt + 1] = arg_ or 'nil'
	end

	if  FOO.fast() then
		FOO.debug( "[" .. path_ .. "] " .. table_concat(new_txt, '|') )
	end

	local local_write_ = false
	if  name == "error" then
		local_write_ = true   --错误日志只打本地
		new_txt[ #new_txt + 1] = ngx.ctx.request_uri_s7 or 'nil'
		if  ngx.ctx.timer_at == 1 then
            new_txt[ #new_txt + 1] = 'timer'            
        else
            new_txt[ #new_txt + 1] = ngx.var.request_uri    or 'nil'
        end
	end

	FOO.log2fileBuff( path_,  table_concat(new_txt, '|'), local_write_ );
end;


--不打印IP
FOO.day_list_simple = function( name, ... )
	local path_ = FOO.root_dir .. name .. '_' .. os_date( "%Y%m%d" ) .. '.log';
	local new_txt =	{
		os_date('%X'),
		'-',
		FOO.get_all_cost(),
	}

	for i = 1, select("#", ...) do
		local arg_ = select(i, ...)
		new_txt[ #new_txt + 1] = arg_ or 'nil'
	end

	if  FOO.fast() then
		FOO.debug( "[" .. path_ .. "] " .. table_concat(new_txt, '|') )
	end

	local local_write_ = false
	if  name == "error" then
		local_write_ = true   --错误日志只打本地
		new_txt[ #new_txt + 1] = ngx.ctx.request_uri_s7 or 'nil'
		if  ngx.ctx.timer_at == 1 then
            new_txt[ #new_txt + 1] = 'timer'            
        else
            new_txt[ #new_txt + 1] = ngx.var.request_uri    or 'nil'
        end
	end

	FOO.log2fileBuff( path_, table_concat(new_txt, '|'), local_write_ );
end;



--打印按天日志(不打印额外信息)
FOO.day_list_short = function( name, ... )
	local path_ = FOO.root_dir .. name .. '_' .. os_date( "%Y%m%d" ) .. '.log';
	local new_txt =	{
		FOO.get_all_cost(),
	}

	for i = 1, select("#", ...) do
		local arg_ = select(i, ...)
		new_txt[ #new_txt + 1] = arg_ or 'nil'
	end

	if  FOO.fast() then
		FOO.debug( "[" .. path_ .. "] " .. table_concat(new_txt, '|') )
	end

	local local_write_ = false
	if  name == "error" then
		local_write_ = true   --错误日志只打本地
		new_txt[ #new_txt + 1] = ngx.ctx.request_uri_s7 or 'nil'
		if  ngx.ctx.timer_at == 1 then
            new_txt[ #new_txt + 1] = 'timer'            
        else
            new_txt[ #new_txt + 1] = ngx.var.request_uri    or 'nil'
        end
	end

	FOO.log2fileBuff( path_, table_concat(new_txt, '|'), local_write_ );
end;


--打印按天日志(不打印额外信息-前面一点都不要)
FOO.day_list_clean = function( name, ... )
	local path_ = FOO.root_dir .. name .. '_' .. os_date( "%Y%m%d" ) .. '.log';

	local new_txt =	{}

	for i = 1, select("#", ...) do
		local arg_ = select(i, ...)
		new_txt[ #new_txt + 1] = arg_ or 'nil'
	end

	if  FOO.fast() then
		FOO.debug( "[" .. path_ .. "] " .. table_concat(new_txt, '|') )
	end

	local local_write_ = false
	if  name == "error" then
		local_write_ = true   --错误日志只打本地
		new_txt[ #new_txt + 1] = ngx.ctx.request_uri_s7 or 'nil'
		if  ngx.ctx.timer_at == 1 then
            new_txt[ #new_txt + 1] = 'timer'            
        else
            new_txt[ #new_txt + 1] = ngx.var.request_uri    or 'nil'
        end
	end

	FOO.log2fileBuff( path_,  table_concat(new_txt, '|'), local_write_ );
end;


--纯输出
FOO.record_day = function( name, content )
	if name and content then
		local path_ = FOO.root_dir .. name .. '_' .. os_date( "%Y%m%d" ) .. '.log';
		FOO.log2fileBuff( path_, content );
	end
end


--打印按天日志 logs/mayname_20160909.log
FOO.day = function( name, txt )
	local path_ = FOO.root_dir .. name .. '_' .. os_date( "%Y%m%d" ) .. '.log';
	local new_txt =	{
		os_date('%X'),
		FOO.align_ip(ngx.ctx.xff or ngx.var.remote_addr or "nil"),
		FOO.get_all_cost(),
		txt,
	}

	if  FOO.fast() then
		FOO.debug( "[" .. path_ .. "] " .. table_concat(new_txt, '|') )
	end

	local local_write_ = false
	if  name == "error" then
		local_write_ = true   --错误日志只打本地
		new_txt[ #new_txt + 1] = ngx.ctx.request_uri_s7 or 'nil'
		if  ngx.ctx.timer_at == 1 then
			new_txt[ #new_txt + 1] = 'timer'
		else
			new_txt[ #new_txt + 1] = ngx.var.request_uri    or 'nil'
		end
	end

	FOO.log2fileBuff( path_, table_concat(new_txt, '|'), local_write_ );

end



--只获得结果 不打印 (远程日志使用 暂时不用)
FOO.day_log_params = function( name, txt )
	local path_ = FOO.root_dir .. name .. '_' .. os_date( "%Y%m%d" ) .. '.log';

	--local new_txt = os_date('%X') .. '|' .. FOO.align_ip(ngx.ctx.xff or ngx.var.remote_addr or "nil")
	--		.. '|' .. FOO.get_all_cost() .. '|'  .. txt;

	local new_txt = table_concat( { os_date('%X'), FOO.align_ip(ngx.ctx.xff or ngx.var.remote_addr or "nil"),
									FOO.get_all_cost(), txt }, '|' )

	if  name == "error" then
		new_txt = new_txt .. "|" .. (ngx.ctx.request_uri_s7 or 'nil') .. "|" .. (ngx.var.request_uri or 'nil')
	end
	return path_, new_txt
end



--打印按天日志 logs/mayname_20160909.log (旧版本，未再使用)
FOO.day2 = function( name, txt )
	local path_ = FOO.root_dir .. name .. '_' .. os_date( "%Y%m%d" ) .. '.log';
	--local new_txt = os_date('%X') .. '|' .. FOO.align_ip(ngx.ctx.xff or ngx.var.remote_addr or "nil")
	--		.. '|' .. FOO.get_all_cost() .. '|'  .. txt;
	local new_txt = table_concat( { os_date('%X'), FOO.align_ip(ngx.ctx.xff or ngx.var.remote_addr or "nil"),
									FOO.get_all_cost(), txt }, '|' )
	FOO.log2fileBuff( path_, new_txt );
end



FOO.get_space = function(level)
	local s = ''
	for i=1, level do
		s = s .. '    '
	end
	return s
end

FOO.table2string = function(tab, level)
    level = level or 0
	if type(tab) == "number" then
		return tonumber(tab)
	elseif type(tab) == "string" then
		string.gsub(tab, '"', '\\\"')
		return string_format('"%s"', tostring(tab))
	elseif type(tab) == "table" then
		local s = '{\n'
		level = level + 1
		for k, v in pairs(tab) do
			if type(k) == "string" and not tonumber(k) then
				s = string_format("%s%s%s = %s,\n",s, FOO.get_space(level), k, FOO.table2string(v, level))
			else
				s = string_format("%s%s[%s] = %s,\n",s, FOO.get_space(level), FOO.table2string(k, level), FOO.table2string(v, level))
			end
		end
		return string_format("%s%s} ",s, FOO.get_space(level-1))
	else -- "function"  "nil" "boolean"
		return tostring(tab)
	end
end

FOO.format_args = function(str_format, ...)
	if type(str_format) ~= 'string' then
		return ""
	else
		local tab = {...}
		local n = 0
		return string.gsub(str_format, '%%%%', function() -- 替换字符串的%%
			n = n + 1
			return FOO.table2string(tab[n])
		end)
	end
end

FOO.loggerdebug = function (str_format, ...)
	if ngx.ctx.log ~= 0 then
		FOO.debug(FOO.format_args(str_format, ...))
	end
end

return FOO;
